/*
   \file  config.cc
   \brief libgist driver program config file API.

   This file defines the non-inline member functions of the
   gistdrv::config class. See its header for more info.
*/

/*
   This file is part of libgist.

   libgist is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2 of the License, or (at your
   option) any later version.

   libgist is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License
   along with libgist; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

/*
   REVISION HISTORY

   $HeadURL: http://libgist.googlecode.com/svn/branches/lazebnik_dev/drv/config.cc $
   $Id: config.cc 51 2011-08-29 17:41:39Z libgist@gmail.com $
*/

//------------------------------ HEADERS --------------------------------

// gistdrv
#include "config.hh"
#include "file_utils.hh"

// Boost
#include <boost/lambda/lambda.hpp>
#include <boost/bind.hpp>

// Standard C++
#include <algorithm>

// Standard C
#include <string.h>
#include <stdlib.h>

//----------------------------- NAMESPACE -------------------------------

namespace gistdrv {

//------------------------------ GLOBALS --------------------------------

namespace {

enum error_codes {
   NO_SUCH_FILE = 11,
   BAD_CONFIG,
   PYTHON_ERROR,
} ;

// A variable to hold the config file name (used when reporting errors).
//
// DEVNOTE: Instead of a global variable, we could have made this a
// member variable of the config class. However, that would require
// either adding an API to allow config's exception classes to retrieve
// this value or declaring the exceptions as config's friends. Of these
// two possibilities, the latter would need more work (whenever we add a
// new exception, we'd have to remember to make it a friend of config).
// Therefore, adding an accessor API would be the way to go.
//
// However, since this variable is used only by config's exception
// classes, there is no need to make it part of config and add an
// accessor API. Much easier to simply use this static global.
std::string g_config_file_name ;

} // end of local anonymous namespace encapsulating above constants

//-------------------- INITIALIZATION AND CLEAN-UP ----------------------

config:: config(){}
config::~config(){}

// Load libgist settings by executing the Python script specified on the
// command line.
void
config::
load(const std::string& config_file, const std::vector<std::string>& params)
{
   if (!exists(config_file))
      throw no_such_file(config_file) ;

   config& conf = instance() ;
   g_config_file_name = config_file ;

   namespace py = boost::python ;
   try
   {
      // Start Python interpreter and get access to its main namespace
      Py_Initialize() ;
      py::object main_module = py::import("__main__") ;
      conf.m_main_namespace  = main_module.attr("__dict__") ;

      // Setup command line arguments for the Python script
      using boost::bind ;
      using boost::lambda::_1 ;
      int    argc = params.size()  + 1 ;
      char** argv = new char*[argc + 1] ;
      argv[0] = strdup(config_file.c_str()) ;
      std::transform(params.begin(), params.end(), argv + 1,
                     bind(strdup, bind(&std::string::c_str, _1))) ;
      argv[argc] = 0 ;
      PySys_SetArgv(argc, argv) ;

      // Clean-up faux command line after passing to Python
      std::for_each(argv, argv + argc, free) ;
      delete[] argv ;

      // Okay, everything's ready; let's load the libgist settings by
      // running the Python code...
      py::exec_file(config_file.c_str(), conf.m_main_namespace) ;
   }
   catch (const py::error_already_set&)
   {
      throw python_error() ;
   }
}

//---------------------------- EXCEPTIONS -------------------------------

// Some helper functions
namespace {

// Extract Python exception info courtesy
// http://thejosephturner.com/blog/2011/06/15/embedding-python-in-c-applications-with-boostpython-part-2/
std::string parse_python_exception()
{
   namespace py = boost::python ;
   std::string error_info("Python error") ;

   PyObject* type  = 0 ;
   PyObject* value = 0 ;
   PyObject* trace = 0 ;
   PyErr_Fetch(&type, &value, &trace) ;

   if (type) {
      py::handle<> h(type) ;
      py::str s(h) ;
      py::extract<std::string> e(s) ;
      error_info += "\n\tType:  " ;
      error_info += e.check() ? e() : "unknown" ;
   }
   if (value) {
      py::handle<> h(value) ;
      py::str s(h) ;
      py::extract<std::string> e(s) ;
      error_info += "\n\tValue: " ;
      error_info += e.check() ? e() : "unknown" ;
   }
   if (trace) {
      try
      {
         py::object fmt_tb(py::import("traceback").attr("format_tb")) ;
         py::object tb_list(fmt_tb(py::handle<>(trace))) ;
         py::extract<std::string> traceback(py::str("\n").join(tb_list)) ;
         error_info += "\n\tTraceback: " ;
         error_info += traceback.check() ? ("\n" + traceback()) : "unavailable";
      }
      catch (const py::error_already_set&)
      {
         error_info += "\n\tTraceback: unavailable" ;
      }
   }
   return error_info ;
}

// Just a convenience function to return config file name (reduces amount
// of typing and helps keep code all on one line.
inline std::string conf()
{
   return g_config_file_name + ": " ;
}

} // end of local anonymous namespace encapsulating above helpers

config::no_such_file::no_such_file(const std::string& file_name)
   : exception(NO_SUCH_FILE, file_name + ": no such config file")
{}

config::bad_config::
bad_config(const std::string& property, const std::string& msg)
   : exception(BAD_CONFIG, conf() + property + ": bad config; " + msg)
{}

config::python_error::python_error()
   : exception(PYTHON_ERROR, conf() + parse_python_exception())
{}

//-----------------------------------------------------------------------

} // end of namespace encapsulating this file's definitions

/* So things look consistent in everyone's emacs... */
/* Local Variables: */
/* indent-tabs-mode: nil */
/* End: */
